package cn.com.idmy.auth.rbac;

import cn.com.idmy.auth.annotation.RbacOp;
import cn.com.idmy.auth.annotation.RbacOr;
import cn.com.idmy.auth.annotation.RbacPermission;
import cn.com.idmy.auth.annotation.RbacRole;
import cn.com.idmy.auth.context.Contexts;
import cn.com.idmy.auth.exception.AuthException;
import cn.com.idmy.base.util.Assert;
import lombok.Data;
import lombok.experimental.Accessors;
import org.dromara.hutool.core.annotation.AnnotationUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;

@Data
@Accessors(fluent = true, chain = false)
public class RbacManager<ID> {
    protected volatile RbacDao<ID> rbacDao;

    @Nullable
    protected static <T extends Annotation> T getAnnotation(@NotNull Method method, @NotNull Class<? extends Annotation> annotationClass) {
        Annotation annotation = AnnotationUtil.getAnnotation(method, annotationClass);
        if (annotation == null) {
            return (T) AnnotationUtil.getAnnotation(method.getDeclaringClass(), annotationClass);
        } else {
            return (T) annotation;
        }
    }

    public void init() {
        Assert.notNull(rbacDao, "rbacDao is null");
    }

    @NotNull
    public Collection<String> listRoles(@NotNull ID id) {
        return rbacDao.listRoles(id, Contexts.accountType());
    }

    public boolean hasAllRole(@NotNull ID id, @NotNull String... roles) {
        return rbacDao.hasAllRoles(id, Contexts.accountType(), Set.of(roles));
    }

    public void checkAllRoles(@NotNull ID id, @NotNull String... roles) {
        boolean b = hasAllRole(id, roles);
        Assert.isTrue(b, () -> new RbacRoleException(roles[0], Contexts.accountType()));
    }

    public boolean hasAnyRole(@NotNull ID id, @NotNull String... roles) {
        return rbacDao.hasAnyRoles(id, Contexts.accountType(), Set.of(roles));
    }

    public void checkAnyRoles(@NotNull ID id, @NotNull String... roles) {
        boolean b = hasAnyRole(id, roles);
        Assert.isTrue(b, () -> new RbacRoleException(roles[0], Contexts.accountType()));
    }

    public void checkAllRoles(@NotNull ID id, RbacRole at) {
        String[] roles = at.value();
        if (at.op() == RbacOp.AND) {
            checkAllRoles(id, roles);
        } else {
            checkAnyRoles(id, roles);
        }
    }

    public Collection<String> listPermissions(@NotNull ID id) {
        return rbacDao.listPermissions(id, Contexts.accountType());
    }

    public boolean hasAllPermissions(@NotNull ID id, @NotNull String... permissions) {
        return rbacDao.hasAllPermissions(id, Contexts.accountType(), Set.of(permissions));
    }

    public void checkAllPermissions(@NotNull ID id, @NotNull String... permissions) {
        boolean b = hasAllPermissions(id, permissions);
        Assert.isTrue(b, () -> new RbacPermissionException(permissions[0], Contexts.accountType()));
    }

    public boolean hasAnyPermissions(@NotNull ID id, @NotNull String... permissions) {
        return rbacDao.hasAnyPermissions(id, Contexts.accountType(), Set.of(permissions));
    }

    public void checkAnyPermissions(@NotNull ID id, @NotNull String... permissions) {
        boolean b = hasAnyPermissions(id, permissions);
        Assert.isTrue(b, () -> new RbacPermissionException(permissions[0], Contexts.accountType()));
    }

    public void checkAllPermissions(@NotNull ID id, RbacPermission at) {
        var permissions = at.value();
        try {
            if (at.op() == RbacOp.AND) {
                checkAllPermissions(id, permissions);
            } else {
                checkAnyPermissions(id, permissions);
            }
        } catch (RbacPermissionException e) {
            for (var role : at.orRole()) {
                if (hasAllRole(id, role)) {
                    return;
                }
            }
            throw e;
        }
    }

    public void check(@NotNull ID id, @NotNull Method method) {
        RbacRole role = getAnnotation(method, RbacRole.class);
        if (role != null) {
            checkAllRoles(id, role);
        }

        RbacPermission permission = getAnnotation(method, RbacPermission.class);
        if (permission != null) {
            checkAllPermissions(id, permission);
        }

        RbacOr or = getAnnotation(method, RbacOr.class);
        if (or != null) {
            check(id, or);
        }
    }

    public void check(@NotNull ID id, @NotNull RbacOr or) {
        var exceptions = new ArrayList<AuthException>();
        var roles = or.roles();
        for (RbacRole role : roles) {
            try {
                checkAllRoles(id, role);
                return;
            } catch (RbacRoleException e) {
                exceptions.add(e);
            }
        }

        var permissions = or.permissions();
        for (RbacPermission permission : permissions) {
            try {
                checkAllPermissions(id, permission);
                return;
            } catch (RbacPermissionException e) {
                exceptions.add(e);
            }
        }

        if (!exceptions.isEmpty()) {
            throw exceptions.get(0);
        }
    }
}
